package com.meizu.hjcool7.androidorm;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.meizu.hjcool7.androidorm.annotation.Column;
import com.meizu.hjcool7.androidorm.annotation.ID;
import com.meizu.hjcool7.androidorm.annotation.Table;
import com.meizu.hjcool7.androidorm.exception.ParseEntityException;
import android.database.Cursor;

public class Util
{
	private static HashMap<Class<?>, String> mTableNameCache;
	private static HashMap<Field, String> mColumnNameCache;
	private static HashMap<Field, ColumnType> mColumnTypeCache;
	private static HashMap<Class<?>, Field> mIdFieldCache;
	
	static
	{
		mTableNameCache = new HashMap<Class<?>, String>();
		mColumnNameCache = new HashMap<Field, String>();
		mIdFieldCache = new HashMap<Class<?>, Field>();
		mColumnTypeCache = new HashMap<Field, ColumnType>();
	}
	
	private Util()
	{
		throw new Error("Cannot instantiate this class");
	}
	
	public static interface FieldIterator
	{
		void iteratre(StringBuffer sql, Field field,String columnName,ColumnType columnType,Object obj) throws Exception;
	}
	
	public static StringBuffer createStringBuffer(String ...strings)
	{
		StringBuffer sb = new StringBuffer();
		if (strings != null)
		{
			for (String string : strings)
			{
				if (string != null)
				{
					sb.append(string);
				}
			}
		}
		return sb;
	}
	
	public static void append(StringBuffer sb,String ...strings)
	{
		if (sb == null || strings == null)
		{
			return;
		}
		for (String string : strings)
		{
			if (string != null)
			{
				sb.append(string);
			}
		}
	}
	
	public static boolean addString(StringBuffer sb,String str,boolean isFirst)
	{
		if (!isFirst)
		{
			if (sb != null && str != null)
			{
				sb.append(str);
			}
		}
		return false;
	}
	
	public static boolean addComma(StringBuffer sb,boolean isFirst)
	{
		return addString(sb, ",",isFirst);
	}
	
	public static String getTableName(Class<?> entity)
	{
		if (entity == null)
		{
			throw new ParseEntityException("Cannot get table name for null");
		}
		String tableName = mTableNameCache.get(entity);
		if (tableName == null)
		{
			Table table = entity.getAnnotation(Table.class);
			if (table == null)
			{
				throw new ParseEntityException("Cannot get table name for " + entity.getName());
			}
			if (!table.value().equals(""))
			{
				tableName = table.value();
			}
			else
			{
				tableName = entity.getSimpleName();
			}
			mTableNameCache.put(entity, tableName);
		}
		return tableName;
	}
	
	public static String getColumnName(Field field)
	{
		if (field == null)
		{
			return null;
		}
		String columnName = mColumnNameCache.get(field);
		if (columnName == null)
		{
			Column column = field.getAnnotation(Column.class);
			if (column == null)
			{
				return null;
			}
			if (!column.value().equals(""))
			{
				columnName = column.value();
			}
			else
			{
				columnName = field.getName();
			}
			mColumnNameCache.put(field, columnName);
		}
		return columnName;
	}
	
	public static ColumnType getColumnType(Field field)
	{
		if (field == null)
		{
			return null;
		}
		ColumnType type = mColumnTypeCache.get(field);
		if (type == null)
		{
			Class<?> fieldType = field.getType();
			if (String.class.isAssignableFrom(fieldType))
			{
				type = ColumnType.TEXT;
			}
			else if (int.class.isAssignableFrom(fieldType) || 
					Integer.class.isAssignableFrom(fieldType) ||
					boolean.class.isAssignableFrom(fieldType) ||
					Boolean.class.isAssignableFrom(fieldType))
			{
				type = ColumnType.INTEGER;
			}
			else if (double.class.isAssignableFrom(fieldType) || 
					Double.class.isAssignableFrom(fieldType) ||
					float.class.isAssignableFrom(fieldType) ||
					Float.class.isAssignableFrom(fieldType))
			{
				type = ColumnType.REAL;
			}
			if (type != null)
			{
				mColumnTypeCache.put(field, type);
			}
			else
			{
				throw new ParseEntityException("Invalid column type: " + field.getName());
			}
		}
		return type;
	}
	
	public static Field getIdField(Class<?> entity)
	{
		if (entity == null)
		{
			return null;
		}
		Field idField = mIdFieldCache.get(entity);
		if (idField == null)
		{
			Field[] fileds = entity.getDeclaredFields();
			for (Field field : fileds)
			{
				if (field.getAnnotation(ID.class) != null)
				{
					idField = field;
					break;
				}
			}
			if (idField != null)
			{
				mIdFieldCache.put(entity, idField);
			}
			else
			{
				throw new ParseEntityException("id column not found: " + entity.getName());
			}
		}
		return idField;
	}
	
	public static String getFieldValueString(Field field,Object obj,ColumnType columnType)
	{
		try
		{
			field.setAccessible(true);
			Object value = field.get(obj);
			switch (columnType)
			{
				case INTEGER:
				case REAL:
				{
					if (value != null)
					{
						return value.toString();
					}
					else
					{
						return "0";
					}
				}
				case TEXT:
				{
					if (value != null)
					{
						return value.toString();
					}
					else
					{
						return "";
					}
				}
				default:
					throw new ParseEntityException("");
			}
		}
		catch (Exception e)
		{
			throw new ParseEntityException(e);
		}
	}
	
	public static String getFieldValueString(Field field,Object obj)
	{
		return getFieldValueString(field, obj, Util.getColumnType(field));
	}
	
	public static void checkColumnType(Field field)
	{
		Util.getColumnType(field);
	}
	
	public static String getIdColumnName(Class<?> entity)
	{
		return Util.getColumnName(Util.getIdField(entity));
	}
	
	public static String getIdFieldValueString(Object obj)
	{
		return Util.getFieldValueString(Util.getIdField(obj.getClass()), obj);
	}
	
	public static void iterateColumn(Class<?> entity,StringBuffer sql,Object obj,FieldIterator iterator)
	{
		Field[] fileds = entity.getDeclaredFields();
		boolean isFirstColumn = (sql == null) ? false : true;
		for (Field field : fileds)
		{
			String columnName = Util.getColumnName(field);
			if (columnName == null)
			{
				continue;
			}
			ColumnType columnType = Util.getColumnType(field);
			if (sql != null)
			{
				isFirstColumn = Util.addComma(sql, isFirstColumn);
			}
			try
			{
				iterator.iteratre(sql,field, columnName, columnType,obj);
			}
			catch (Exception e)
			{
				throw new ParseEntityException(e);
			}
		}
		if (isFirstColumn)
		{
			throw new ParseEntityException("column not found: " + entity.getName());
		}
	}
	
	public static void setFieldValue(Field field,Object obj,ColumnType columnType,String columnName,Cursor cursor) throws IllegalArgumentException, IllegalAccessException
	{
		if (field == null || obj == null || columnType == null || columnName == null || cursor == null)
		{
			return;
		}
		int columnIndex = cursor.getColumnIndexOrThrow(columnName);
		if(cursor.getType(columnIndex) != columnType.sqliteType)
		{
			throw new ParseEntityException("Type mismatch:" + field.getName());
		}
		field.setAccessible(true);
		Class<?> fieldType = field.getType();
		switch (columnType)
		{
			case INTEGER:
				if (int.class.isAssignableFrom(fieldType) || Integer.class.isAssignableFrom(fieldType))
				{
					field.set(obj, cursor.getInt(columnIndex));
				}
				else if (boolean.class.isAssignableFrom(fieldType) || Boolean.class.isAssignableFrom(fieldType))
				{
					field.set(obj, (cursor.getInt(columnIndex) != 0));
				}
				break;
			case REAL:
				if (double.class.isAssignableFrom(fieldType) || Double.class.isAssignableFrom(fieldType))
				{
					field.set(obj, cursor.getDouble(columnIndex));
				}
				else if (float.class.isAssignableFrom(fieldType) || Float.class.isAssignableFrom(fieldType))
				{
					field.set(obj, cursor.getFloat(columnIndex));
				}
				break;
			case TEXT:
				field.set(obj, cursor.getString(columnIndex));
				break;
		}
	}
	
	public static <T> T getResult(final Cursor cursor,Class<T> entity) throws InstantiationException, IllegalAccessException
	{
		if (cursor == null || cursor.getCount() < 1 || entity == null)
		{
			return null;
		}
		cursor.moveToNext();
		T obj = entity.newInstance();
		Util.iterateColumn(entity, null, obj, new FieldIterator()
		{
			public void iteratre(StringBuffer sql, Field field, String columnName, ColumnType columnType,Object obj) throws Exception
			{
				Util.setFieldValue(field, obj, columnType, columnName, cursor);
			}
		});
		return obj;
	}
	
	public static <T> List<T> getResults(final Cursor cursor,Class<T> entity) throws InstantiationException, IllegalAccessException
	{
		List<T> result = new ArrayList<T>();
		if (cursor == null || cursor.getCount() < 1 || entity == null)
		{
			return result;
		}
		while(cursor.moveToNext())
		{
			T obj = entity.newInstance();
			Util.iterateColumn(entity, null, obj, new FieldIterator()
			{
				public void iteratre(StringBuffer sql, Field field, String columnName, ColumnType columnType,Object obj) throws Exception
				{
					Util.setFieldValue(field, obj, columnType, columnName, cursor);
				}
			});
			result.add(obj);
		}
		return result;
	}
	
	public static Map<String, Object> getResult(Cursor cursor)
	{
		Map<String, Object> record = new HashMap<String, Object>();
		if (cursor == null || cursor.getCount() < 1)
		{
			return record;
		}
		cursor.moveToNext();
		int columnCount = cursor.getColumnCount();
		for (int i = 0;i < columnCount;i++)
		{
			String columnName = cursor.getColumnName(i);
			switch (cursor.getType(i))
			{
				case Cursor.FIELD_TYPE_STRING:
				{
					String value = cursor.getString(i);
					record.put(columnName, value);
					break;
				}
				case Cursor.FIELD_TYPE_FLOAT:
				{
					Double value = cursor.getDouble(i);
					record.put(columnName, value);
				}
				case Cursor.FIELD_TYPE_INTEGER:
				{
					Integer value = cursor.getInt(i);
					record.put(columnName, value);
				}
				default:
					break;
			}
		}
		return record;
	}
	
	public static List<Map<String, Object>> getResults(Cursor cursor)
	{
		List<Map<String, Object>> result = new ArrayList<Map<String,Object>>();
		if (cursor == null || cursor.getCount() < 1)
		{
			return result;
		}
		while(cursor.moveToNext())
		{
			Map<String, Object> record = new HashMap<String, Object>();
			int columnCount = cursor.getColumnCount();
			for (int i = 0;i < columnCount;i++)
			{
				String columnName = cursor.getColumnName(i);
				switch (cursor.getType(i))
				{
					case Cursor.FIELD_TYPE_STRING:
					{
						String value = cursor.getString(i);
						record.put(columnName, value);
						break;
					}
					case Cursor.FIELD_TYPE_FLOAT:
					{
						Double value = cursor.getDouble(i);
						record.put(columnName, value);
					}
					case Cursor.FIELD_TYPE_INTEGER:
					{
						Integer value = cursor.getInt(i);
						record.put(columnName, value);
					}
					default:
						break;
				}
			}
		}
		return result;
	}
	
	public static List<String> getCreateSqls(Class<?>[] entities)
	{
		List<String> createSqls = new ArrayList<String>();
		if (entities == null)
		{
			return createSqls;
		}
		for (Class<?> entity : entities)
		{
			StringBuffer createSql = createStringBuffer("CREATE TABLE IF NOT EXISTS ",Util.getTableName(entity)," (");
			Util.iterateColumn(entity, createSql, null,new FieldIterator()
			{
				public void iteratre(StringBuffer sql, Field field, String columnName, ColumnType columnType,Object obj)
				{
					Util.append(sql, columnName, " " , columnType.toString());
					if (field.getAnnotation(ID.class) != null)
					{
						sql.append(" primary key");
					}
				}
			});
			createSql.append(")");
			createSqls.add(createSql.toString());
		}
		return createSqls;
	}
}
